package com.google.code.guava.reflect;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.primitives.Primitives;

public final class Classes{

    private Classes(){}

    public static Iterable<? extends Class<?>> classHierarchyOf(
        final Class<?> clazz){
        return classHierarchyUntilMatch(clazz,
            Predicates.<Class<?>> alwaysTrue());
    }

    public static Iterable<? extends Class<?>> classHierarchyWithStopClass(
        final Class<?> clazz, final Class<?> stopClass){
        final Predicate<Class<?>> predicate =
            stopClass == null
            ? Predicates.<Class<?>> alwaysTrue()
            : Predicates.<Class<?>> equalTo(stopClass);
            return classHierarchyUntilMatch(clazz, predicate);
    }

    public static Iterable<? extends Class<?>> classHierarchyUntilMatch(
        final Class<?> clazz, final Predicate<Class<?>> predicate){
        checkNotNull(predicate);
        Class<?> current = checkNotNull(clazz);
        final Builder<Class<?>> builder = ImmutableList.builder();
        while(current != null && predicate.apply(current)){
            builder.add(current);
            current = current.getSuperclass();
        }
        return builder.build();
    }

    /**
     * Checks whether the first class is the wrapper class associated with the
     * primitive second class. Returns true if and only if the second class is
     * primitive and the first is it's wrapper
     * 
     * @return
     */
    public static boolean isWrapperOfPrimitive(final Class<?> wrapper,
        final Class<?> primitive){

        return checkNotNull(primitive).isPrimitive() && wrapper != primitive
               && Primitives.unwrap(wrapper) == primitive;
    }

    public static boolean equivalentWhenAutoBoxing(final Class<?> type1,
        final Class<?> type2){
        return checkNotNull(type1).equals(checkNotNull(type2))
        || isWrapperOfPrimitive(type1, type2)
        || isWrapperOfPrimitive(type2, type1);
    }

}
